﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Runtime.Serialization;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using GRV11;

namespace gpuimp.core
{
    public enum BlendMode
    {
        Normal, //Standard blending
        Multiply, //multiply color values
        Add,  //add color values
        Overlay //mix dark and light equally
    }

    /// <summary>
    /// Small helper class for handling vertices used for drawing
    /// </summary>
    [Serializable]
    public class Vertex
    {
        public float X, Y, Z;
        public float U, V;

        public Vertex(float x, float y, float z, float u, float v)
        {
            X = x;
            Y = y;
            Z = z;
            U = u;
            V = v;
        }
    }

    [Serializable]
    public class Layer : ICloneable
    {
        private bool isLocked;
        private bool isVisible;
        private BlendMode blendMode;
        private int id;
        private float opacity;
        private float rotation;
        private PointF size;
        private Point resolution;
        private PointF position;
        private string name;
        private List<Modifier> modifierList;
        private Bitmap baseTexture;
        private Bitmap finalTexture;
        private Bitmap thumbnail;
        [NonSerialized]
        public GRTexture grBaseTexture;
        [NonSerialized]
        public GRTexture grFinalTexture;
        [NonSerialized]
        private GRTexture grThumbnail;
        private List<Vertex> mesh; //there should only be two necessary, but this way its more flexible for future ideas
        private int shader;

        private string fragProg = @"   uniform sampler2D Texture0;
                                       varying vec2 texCoord;

                                       void main(void)
                                       {
                                           gl_FragColor = tex2D(Texture0, texCoord);
                                       }";

        private string vertProg = @"   varying vec2  texCoord;

                                       void main(void)
                                       {
                                          gl_Position = ftransform();
                                          texCoord    = gl_MultiTexCoord0.xy;
                                       }";
#region Properties
        /// <summary>
        /// If the layer is locked, its not to be moved around or have its alpha changed
        /// </summary>
        public bool IsLocked
        {
            get { return isLocked; }
            set { isLocked = value; }
        }

        /// <summary>
        /// Sets whether layer is drawn or not
        /// </summary>
        public bool IsVisible
        {
            get { return isVisible; }
            set { isVisible = value; }
        }

        /// <summary>
        /// Blend mode used when drawing layer
        /// </summary>
        public BlendMode BlendMode
        {
            set { blendMode = value; }
        }

        /// <summary>
        /// ID assigned to this layer
        /// </summary>
        public int ID
        {
            get { return id; }
            set { id = value; }
        }

        /// <summary>
        /// Layer is fully solid at 1.0, fully transparent at 0.0, and interpolates inbetween there
        /// </summary>
        public float Opacity
        {
            get { return opacity; }
            set
            {
                opacity = value;
                if (opacity < 0) opacity = 0;
                else if (opacity > 1) opacity = 1;
            }
        }

        /// <summary>
        /// Value in degrees to rotating the layer about an axis perpendicular to the canvas, going through the layers center point
        /// </summary>
        public float Rotation
        {
            get { return rotation; }
            set { rotation = value % 360; }
        }

        /// <summary>
        /// Scaling of the layer. 1.0, 1.0 would be 1:1 pixel relationship to canvas size
        /// </summary>
        public PointF Size
        {
            get { return size; }
            set { if (value != null) size = value; }
        }

        /// <summary>
        /// Size of the layer in pixels
        /// </summary>
        public Point Resolution
        {
            get { return resolution; }
            set { if (value != null) resolution = value; }
        }

        public PointF Position
        {
            get { return position; }
            set { if(value != null) position = value; }
        }

        /// <summary>
        /// Name of the layer
        /// </summary>
        public string Name
        {
            get { return name; }
            set
            {
                if (name != "" && name != null)
                    name = value;
            }
        }

        /// <summary>
        /// List of all modifiers assigned to this layer
        /// </summary>
        public List<Modifier> ModifierList
        {
            get { return modifierList; }
        }

        /// <summary>
        /// Texture representing this layer before any modifiers are applied.
        /// </summary>
        public Bitmap BaseTexture
        {
            get 
            {
                grBaseTexture.SetAsActiveTexture(CanvasManager.gr);
                grBaseTexture.TransferOpenGLTextureDataBackToHostMemoryAndCopyToCompatibleBitmap(CanvasManager.gr, baseTexture);
                return baseTexture; 
            }
            set { baseTexture = value; }
        }

        /// <summary>
        /// Texture representing this layer after all modifiers are applied.
        /// </summary>
        public Bitmap FinalTexture
        {
            get 
            {
                grFinalTexture.SetAsActiveTexture(CanvasManager.gr);
                grFinalTexture.TransferOpenGLTextureDataBackToHostMemoryAndCopyToCompatibleBitmap(CanvasManager.gr, finalTexture);
                return finalTexture; 
            }
            set { finalTexture = value; }
        }

        public Bitmap OutputTexture
        {
            get
            {
                Bitmap b = new Bitmap(resolution.X, resolution.Y);
                if (modifierList.Count == 0)
                {
                    grBaseTexture.SetAsActiveTexture(CanvasManager.gr);
                    grBaseTexture.TransferOpenGLTextureDataBackToHostMemoryAndCopyToCompatibleBitmap(CanvasManager.gr, b);
                }
                else
                {
                    grFinalTexture.SetAsActiveTexture(CanvasManager.gr);
                    grFinalTexture.TransferOpenGLTextureDataBackToHostMemoryAndCopyToCompatibleBitmap(CanvasManager.gr, finalTexture);
                }
                return b;
            }
        }

        /// <summary>
        /// Smaller version of FinalTexture used for thumbnails
        /// </summary>
        public Bitmap Thumbnail
        {
            get { return thumbnail; }
        }
#endregion

#region Constructors/Deconstructors
        /// <summary>
        /// Create a blank layer with the given resolution (typically canvas size)
        /// </summary>
        /// <param name="pixelSize"></param>
        public Layer(Point pixelSize)
        {
            resolution = pixelSize;
            try
            {
                baseTexture = new Bitmap(resolution.X, resolution.Y);
                finalTexture = new Bitmap(resolution.X, resolution.Y);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }

            thumbnail = new Bitmap(128, 128);

            isLocked = false;
            isVisible = true;
            blendMode = BlendMode.Normal;
            Random rand = new Random();
            id = rand.Next();
            opacity = 1.0f;
            rotation = 0.0f;
            size = new PointF(1.0f, 1.0f);
            name = "Layer " + id.ToString();
            modifierList = new List<Modifier>();
            grBaseTexture = new GRTexture();
            grFinalTexture = new GRTexture();
            grThumbnail = new GRTexture();
            mesh = new List<Vertex>();

            GR gr = CanvasManager.gr;
            grBaseTexture.CreateBlankTexture(gr, (int)size.X, (int)size.Y);
            grFinalTexture.CreateBlankTexture(gr, (int)size.X, (int)size.Y);
            grThumbnail.CreateBlankTexture(gr, 128, 128);

            //first tri
            mesh.Add(new Vertex(-1, -1, 0, 0, 0));    //bottom left
            mesh.Add(new Vertex(1, 1, 0, 1, 1));      //top right
            mesh.Add(new Vertex(-1, 1, 0, 0, 1));     //top left
            //second tri
            mesh.Add(new Vertex(-1, -1, 0, 0, 0));    //bottom left
            mesh.Add(new Vertex(1, -1, 0, 1, 0));     //bottom right
            mesh.Add(new Vertex(1, 1, 0, 1, 1));      //top right
            
            setupShader();
        }

        /// <summary>
        /// Create a new layer with a specific bitmap to start with
        /// </summary>
        /// <param name="bitmap">Bitmap to use as the base for the layer</param>
        public Layer(Bitmap bitmap)
        {
            isLocked = false;
            isVisible = true;
            blendMode = BlendMode.Normal;
            Random rand = new Random();
            id = rand.Next();
            opacity = 1.0f;
            rotation = 0.0f;
            size = new PointF(1.0f, 1.0f);
            name = "Layer " + id.ToString();
            modifierList = new List<Modifier>();
            baseTexture = bitmap;
            GR gr = CanvasManager.gr;
            grBaseTexture = new GRTexture();
            grFinalTexture = new GRTexture();
            grThumbnail = new GRTexture();

            try
            {
                grBaseTexture.CreateTextureFromBitmap(gr, bitmap, false);
                grFinalTexture.CreateTextureFromBitmap(gr, bitmap, false);
                grThumbnail.CreateBlankTexture(gr, 128, 128);
            }
            catch (NullReferenceException nre)
            {
                Console.WriteLine(nre.Message);
            }
            
            
            resolution = new Point(baseTexture.Width, baseTexture.Height);
            try
            {
                finalTexture = new Bitmap(resolution.X, resolution.Y);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
            
            thumbnail = new Bitmap(bitmap, new Size(128, 128));

            mesh = new List<Vertex>();

            //first tri
            mesh.Add(new Vertex(-1, -1, 0, 0, 0));    //bottom left
            mesh.Add(new Vertex(1, 1, 0, 1, 1));      //top right
            mesh.Add(new Vertex(-1, 1, 0, 0, 1));     //top left
            //second tri
            mesh.Add(new Vertex(-1, -1, 0, 0, 0));    //bottom left
            mesh.Add(new Vertex(1, -1, 0, 1, 0));     //bottom right
            mesh.Add(new Vertex(1, 1, 0, 1, 1));      //top right

            setupShader();

            //temporarily testing gaussian blur modifier
            //Modifier blur = new GaussianBlur();
            //Modifier mirror = new MirrorPlane();
            //Modifier normalMap = new NormalMap();
            //modifierList.Add(blur);
            //modifierList.Add(normalMap);
            //modifierList.Add(mirror);
           
        }

        public object Clone()
        {
            //deep copy
            MemoryStream ms = new MemoryStream();
            BinaryFormatter bf = new BinaryFormatter();
            bf.Serialize(ms, this);
            ms.Position = 0;
            object obj = bf.Deserialize(ms);

            (obj as Layer).BaseTexture = (Bitmap)this.BaseTexture.Clone();
            (obj as Layer).FinalTexture = (Bitmap)this.FinalTexture.Clone();

            (obj as Layer).grBaseTexture = new GRTexture();
            (obj as Layer).grFinalTexture = new GRTexture();

            (obj as Layer).grBaseTexture.CreateTextureFromBitmap(CanvasManager.gr, this.BaseTexture, false);
            (obj as Layer).grFinalTexture.CreateTextureFromBitmap(CanvasManager.gr, this.FinalTexture, false);

            return obj;
        }


        ~Layer()
        {
        }
#endregion

#region Methods
        void setupShader()
        {
            // Load shader
            if (true == CanvasManager.gr.bglCreateProgramObjectARB)
            {
                GRSupport.SupportShaderProgramCreate
                (
                    CanvasManager.gr,
                    vertProg,
                    fragProg,
                    ref this.shader
                );
            }
        }

        public void DrawModifiers(GR gr)
        {
            if (modifierList.Count > 0)
            {
                //get render control
                //height and width of the image
                int width = grBaseTexture.GetWidth();
                int height = grBaseTexture.GetHeight();

                gr.glLoadIdentity();

                gr.glPushMatrix();
                //set to render to texture size
                modifierList[0].setupToolViewport(gr);

                for (int i = 0; i < modifierList.Count; i++)
                {
                    if (modifierList[i].isActive)
                    {
                        //set the modifier shader
                        modifierList[i].ActivatePass1();

                        gr.glClear(GR.GL_COLOR_BUFFER_BIT);

                        //get IDs of the two textures to use
                        int fboTexture = CanvasManager.CurrentCanvas.grFBOTexture.GetOpenGLTextureID();
                        int layerTexture;
                        if(i == 0)
                            layerTexture = grBaseTexture.GetOpenGLTextureID();
                        else
                            layerTexture = grFinalTexture.GetOpenGLTextureID();

                        //bind FBO
                        gr.glBindFramebufferEXT(GR.GL_FRAMEBUFFER_EXT, CanvasManager.CurrentCanvas.FBO[0]);
                        //bind intermediate texture to fbo
                        gr.glFramebufferTexture2DEXT(GR.GL_FRAMEBUFFER_EXT, GR.GL_COLOR_ATTACHMENT0_EXT, GR.GL_TEXTURE_2D, fboTexture, 0);

                        //set to draw to color 0 buffer
                        int[] buffers = { GR.GL_COLOR_ATTACHMENT0_EXT };
                        gr.glDrawBuffers(1, buffers);



                        //set the original texture active
                        if (i == 0)
                            grBaseTexture.SetAsActiveTexture(gr);
                        //else
                        //    grFinalTexture.SetAsActiveTexture(gr);
                        //draw quad with this texture
                        Draw(gr, false, false);

                        if (modifierList[i].shaderPass2 >= 0) //shader exists
                        {
                            //unbind FBO
                            CanvasManager.gr.glBindFramebufferEXT(GR.GL_FRAMEBUFFER_EXT, 0);
                            //remove shader
                            GRSupport.SupportShaderProgramSelect(CanvasManager.gr, 0);

                            //rebind fbo
                            gr.glBindFramebufferEXT(GR.GL_FRAMEBUFFER_EXT, CanvasManager.CurrentCanvas.FBO[1]);
                            //bind final output texture
                            layerTexture = grFinalTexture.GetOpenGLTextureID();
                            gr.glFramebufferTexture2DEXT(GR.GL_FRAMEBUFFER_EXT, GR.GL_COLOR_ATTACHMENT0_EXT, GR.GL_TEXTURE_2D, layerTexture, 0);
                            //switch to copy texture
                            modifierList[i].ActivatePass2();
                            //render quad with intermediate texture
                            CanvasManager.CurrentCanvas.grFBOTexture.SetAsActiveTexture(gr);
                            Draw(CanvasManager.gr, false, false);
                        }
                        else
                        {
                            //unbind FBO
                            CanvasManager.gr.glBindFramebufferEXT(GR.GL_FRAMEBUFFER_EXT, 0);
                            //remove shader
                            GRSupport.SupportShaderProgramSelect(CanvasManager.gr, 0);

                            //rebind fbo
                            gr.glBindFramebufferEXT(GR.GL_FRAMEBUFFER_EXT, CanvasManager.CurrentCanvas.FBO[1]);
                            //bind final output texture
                            layerTexture = grFinalTexture.GetOpenGLTextureID();
                            gr.glFramebufferTexture2DEXT(GR.GL_FRAMEBUFFER_EXT, GR.GL_COLOR_ATTACHMENT0_EXT, GR.GL_TEXTURE_2D, layerTexture, 0);
                            //switch to copy texture
                            modifierList[i].setCopyShader();
                            //render quad with intermediate texture
                            CanvasManager.CurrentCanvas.grFBOTexture.SetAsActiveTexture(gr);
                            Draw(CanvasManager.gr, false, false);
                        }
                    }
                }
                gr.glPopMatrix();
                //unbind framebuffer
                CanvasManager.gr.glBindFramebufferEXT(GR.GL_FRAMEBUFFER_EXT, 0);
                //turn off shader
                GRSupport.SupportShaderProgramSelect(CanvasManager.gr, 0);
            }
        }
        
        public void Draw(GR gr, bool useOwnShader, bool useOwnTexture)
        {
            //if ( modifierList.Count == 0 || !useOwnTexture) //no modifiers, just draw it normally
            {
                if (useOwnTexture)
                {
                    if (grBaseTexture.GetOpenGLTextureID() == 0) return; //dont draw if theres no texture
                    gr.glActiveTexture(GR.GL_TEXTURE0);
                    if (modifierList.Count > 0)
                        grFinalTexture.SetAsActiveTexture(gr);
                    else
                        grBaseTexture.SetAsActiveTexture(gr);
                }
                if(useOwnShader)
                    GRSupport.SupportShaderProgramSelect(CanvasManager.gr, this.shader);

                gr.glPushMatrix();
                gr.glScalef(resolution.X, resolution.Y, 1);
                gr.glBegin(GR.GL_TRIANGLES);
                foreach (Vertex v in mesh)
                {
                    gr.glTexCoord2f(v.U, v.V);
                    gr.glVertex3f(v.X, v.Y, v.Z);
                }
                gr.glEnd();

                gr.glPopMatrix();
                if (useOwnShader)
                    GRSupport.SupportShaderProgramSelect(CanvasManager.gr, 0);
                if (useOwnTexture)
                    gr.glBindTexture(GR.GL_TEXTURE_2D, 0);
            }
        }
#endregion
    }
}
